package com.and.video.service.util;

import com.and.video.domain.exception.ConditionException;
import com.github.tobato.fastdfs.domain.fdfs.FileInfo;
import com.github.tobato.fastdfs.domain.fdfs.MetaData;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.domain.proto.storage.DownloadCallback;
import com.github.tobato.fastdfs.service.AppendFileStorageClient;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import io.netty.util.internal.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.*;


@Component
public class FastDFSUtil {

    @Autowired
    private FastFileStorageClient fastFileStorageClient;
    @Autowired
    private AppendFileStorageClient appendFileStorageClient;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    private static final String DEFAULT_GROUP = "group1";

    private static final String PATH_KEY = "path-key:";

    private static final String UPLOADED_SIZE_KEY = "uploaded-size-key:";

    private static final String UPLOADED_NO_KEY = "uploaded-no-key:";

    @Value("${fdfs.http.storage-addr}")
    private String httpFdfsStorageAddr;

    private static final int SLICE_SIZE = 1024 * 5;
    // 获取文件类型
    public String getFileType(MultipartFile file){
        if(file == null){
            throw new ConditionException("非法文件！");
        }
        // 截取文件名后缀
        String fileName = file.getOriginalFilename();
        int index = fileName.lastIndexOf(".");
        return fileName.substring(index+1);
    }

    //上传
    public String uploadCommonFile(MultipartFile file) throws Exception {
        // 存储文件的附加信息（如作者、创建时间等）
        Set<MetaData> metaDataSet = new HashSet<>();
        // 获取文件类型
        String fileType = this.getFileType(file);
        StorePath storePath = fastFileStorageClient.uploadFile(
                file.getInputStream(), // 文件输入流
                file.getSize(),         // 文件大小（字节）
                fileType,               // 文件扩展名（不含点）
                metaDataSet             // 元数据集合
        );
        return storePath.getPath();
    }

    //上传可以断点续传的文件
    public String uploadAppenderFile(MultipartFile file) throws Exception {
        String fileType = this.getFileType(file);
        StorePath storePath = appendFileStorageClient.uploadAppenderFile(
                DEFAULT_GROUP,           // 文件存储组（如 "group1"）
                file.getInputStream(),   // 文件输入流
                file.getSize(),          // 文件大小（字节）
                fileType                 // 文件扩展名（如 "jpg"）
        );
        return storePath.getPath();  // 返回文件存储路径（如 "M00/00/00/..."）
    }


    // 除了第一个断点, 后面的断点的处理
    public void modifyAppenderFile(
            MultipartFile file,     // 要追加的文件块
            String filePath,        // 初始上传返回的路径（如 "M00/00/00/..."）
            long offset             // 追加的起始位置（字节）
    ) throws Exception {
        appendFileStorageClient.modifyFile(
                DEFAULT_GROUP,      // 文件存储组
                filePath,           // 目标文件路径
                file.getInputStream(), // 追加内容的流
                file.getSize(),     // 追加内容的大小
                offset              // 追加位置偏移量
        );
    }

    /**
     * 实现大文件分片上传与断点续传
     * 第一个分片初始化 FastDFS 上传
     * 后续分片追加到同一文件
     * 使用 Redis 记录上传进度
     */
    public String uploadFileBySlices(
            MultipartFile file,      // 当前上传的文件分片
            String fileMd5,          // 文件唯一标识（MD5值）
            Integer sliceNo,         // 当前分片序号（从1开始）
            Integer totalSliceNo     // 总分片数
    ) throws Exception {

        // 防止前端未正确传递分片信息
        if(file == null || sliceNo == null || totalSliceNo == null){
            throw new ConditionException("参数异常！");
        }
        String pathKey = PATH_KEY + fileMd5;           // 存储文件路径的键（如 "upload:path:d41d8cd98f00b204"）
        String uploadedSizeKey = UPLOADED_SIZE_KEY + fileMd5;  // 已上传总大小的键
        String uploadedNoKey = UPLOADED_NO_KEY + fileMd5;      // 已上传分片数的键

        // 获取已上传大小
        String uploadedSizeStr = redisTemplate.opsForValue().get(uploadedSizeKey);
        Long uploadedSize = 0L;
        if(!StringUtil.isNullOrEmpty(uploadedSizeStr)){
            uploadedSize = Long.valueOf(uploadedSizeStr);
        }

        // 上传第一个分片
        if(sliceNo == 1) {
            String path = this.uploadAppenderFile(file);  // 初始化可追加文件
            if(StringUtil.isNullOrEmpty(path)){
                throw new ConditionException("上传失败！");
            }
            redisTemplate.opsForValue().set(pathKey, path);  // 存储文件路径
            redisTemplate.opsForValue().set(uploadedNoKey, "1");  // 记录已传分片数
        } else {
            String filePath = redisTemplate.opsForValue().get(pathKey);  // 获取文件路径
            if(StringUtil.isNullOrEmpty(filePath)){
                throw new ConditionException("上传失败！");
            }
            this.modifyAppenderFile(file, filePath, uploadedSize);  // 追加内容
            redisTemplate.opsForValue().increment(uploadedNoKey);    // 分片数+1
        }

        // 更新上传进度
        // 修改历史上传分片文件大小
        uploadedSize  += file.getSize();
        redisTemplate.opsForValue().set(uploadedSizeKey, String.valueOf(uploadedSize));


        //如果所有分片全部上传完毕，则清空redis里面相关的key和value
        String uploadedNoStr = redisTemplate.opsForValue().get(uploadedNoKey);
        Integer uploadedNo = Integer.valueOf(uploadedNoStr);
        String resultPath = "";
        if(uploadedNo.equals(totalSliceNo)){  // 所有分片上传完成
            resultPath = redisTemplate.opsForValue().get(pathKey);
            List<String> keyList = Arrays.asList(uploadedNoKey, pathKey, uploadedSizeKey);
            redisTemplate.delete(keyList);  // 清理Redis记录
        }

        return resultPath;
    }

    // 将上传的 MultipartFile 切割为多个固定大小的分片文件
    public void convertFileToSlices(MultipartFile multipartFile) throws Exception{
        //  获取文件类型
        String fileType = this.getFileType(multipartFile);

        // 生成临时文件，将MultipartFile转为File
        // 将 MultipartFile 转为 File 对象以便随机访问
        File file = this.multipartFileToFile(multipartFile);

        // 获取文件大小
        long fileLength = file.length();
        int count = 1;

        // 每次循环处理一个分片
        for(int i = 0; i < fileLength; i += SLICE_SIZE){
            //  随机访问文件
            // 以只读模式打开文件
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            // 定位到当前分片起始位置（字节偏移量）
            randomAccessFile.seek(i);

            // 读取分片数据
            // 最后一个分片可能小于 SLICE_SIZE，len 为实际读取的字节数
            byte[] bytes = new byte[SLICE_SIZE];
            int len = randomAccessFile.read(bytes);

            // 写入分片文件
            String path = "D:\\work\\test\\tmpfile\\" + count + "." + fileType;
            File slice = new File(path);
            FileOutputStream fos = new FileOutputStream(slice);
            fos.write(bytes, 0, len);

            // 关闭资源
            fos.close();
            randomAccessFile.close();
            count++;
        }

        //删除临时文件
        file.delete();
    }


    // 将 MultipartFile 转换为临时 File 对象
    public File multipartFileToFile(MultipartFile multipartFile) throws Exception{
        // 获取原始文件名
        String originalFileName = multipartFile.getOriginalFilename();

        // 分割文件名
        // 示例：
        //"test.mp4" → ["test", "mp4"]
        //"archive.tar.gz" → ["archive", "tar", "gz"]
        String[] fileName = originalFileName.split("\\.");

        // 创建临时文件
        // 如果文件名包含多个点（如 a.b.c），此代码会出错
        File file = File.createTempFile(fileName[0], "." + fileName[1]);

        // 写入文件内容
        multipartFile.transferTo(file);
        return file;
    }


    //删除
    public void deleteFile(String filePath){
        fastFileStorageClient.deleteFile(filePath);
    }


    // 处理在线分段观看视频的请求
    public void viewVideoOnlineBySlices(HttpServletRequest request,
                                        HttpServletResponse response,
                                        String path) throws Exception {
        // 从FastDFS文件存储客户端查询文件信息，DEFAULT_GROUP是默认的存储组，path是文件路径
        FileInfo fileInfo = fastFileStorageClient.queryFileInfo(DEFAULT_GROUP, path);

        // 获取视频文件的总大小（字节数）
        long totalFileSize = fileInfo.getFileSize();

        // 构建完整的文件访问URL，结合FastDFS存储地址和文件路径
        String url = httpFdfsStorageAddr + path;

        // 获取请求中的所有头部信息名称枚举
        Enumeration<String> headerNames = request.getHeaderNames();

        // 创建Map用于存储请求头信息
        Map<String, Object> headers = new HashMap<>();

        // 遍历所有请求头，将其存入Map中
        while(headerNames.hasMoreElements()){
            String header = headerNames.nextElement();
            headers.put(header, request.getHeader(header));
        }

        // 获取Range请求头，该头指定客户端请求的文件字节范围
        String rangeStr = request.getHeader("Range");
        String[] range;

        // 如果Range头为空或null，则默认请求整个文件（从0到文件末尾）
        if(StringUtil.isNullOrEmpty(rangeStr)){
            rangeStr = "bytes=0-" + (totalFileSize-1);
        }

        // 拆分Range字符串，格式通常为"bytes=start-end"
        range = rangeStr.split("bytes=|-");

        // 初始化起始字节位置
        long begin = 0;

        // 如果range数组至少有2个元素（即包含起始位置）
        if(range.length >= 2){
            // 解析起始字节位置
            begin = Long.parseLong(range[1]);
        }

        // 初始化结束字节位置为文件末尾
        long end = totalFileSize-1;

        // 如果range数组有3个元素（即包含结束位置）
        if(range.length >= 3){
            // 解析结束字节位置
            end = Long.parseLong(range[2]);
        }

        // 计算需要返回的数据长度（字节数）
        long len = (end - begin) + 1;

        // 构建Content-Range响应头，格式为"bytes start-end/totalSize"
        String contentRange = "bytes " + begin + "-" + end + "/" + totalFileSize;

        // 设置Content-Range响应头，告知客户端返回的数据范围
        response.setHeader("Content-Range", contentRange);

        // 设置Accept-Ranges响应头，表明服务器支持字节范围请求
        response.setHeader("Accept-Ranges", "bytes");

        // 设置内容类型为MP4视频
        response.setHeader("Content-Type", "video/mp4");

        // 设置响应内容长度
        response.setContentLength((int)len);

        // 设置HTTP状态码为206（部分内容），表示成功处理了范围请求
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);

        // 使用HttpUtil向FastDFS服务器发起GET请求，传递原始请求头，并将响应写入客户端response
        HttpUtil.get(url, headers, response);
    }


    // 使用FastDFS客户端下载文件
    public void downLoadFile(String url, String localPath) {
        // DEFAULT_GROUP: 默认的文件组/卷名
        // url: 要下载的文件在FastDFS服务器上的路径或标识
        // DownloadCallback: 下载回调接口，用于处理下载到的文件流
        fastFileStorageClient.downloadFile(DEFAULT_GROUP, url,
                new DownloadCallback<String>() {
                    @Override
                    public String recv(InputStream ins) throws IOException {
                        // 创建本地文件对象，指定文件保存路径
                        File file = new File(localPath);

                        // 创建文件输出流，用于将下载的数据写入本地文件
                        OutputStream os = new FileOutputStream(file);

                        // 定义读取缓冲区大小（1KB）
                        byte[] buffer = new byte[1024];
                        int len = 0; // 记录每次读取的字节数

                        // 从输入流读取数据并写入输出流
                        // 当读取到文件末尾时，ins.read()返回-1，循环结束
                        while ((len = ins.read(buffer)) != -1) {
                            // 将读取到的数据写入输出流
                            os.write(buffer, 0, len);
                        }

                        // 关闭输出流，确保所有数据都已写入文件
                        os.close();

                        // 关闭输入流，释放资源
                        ins.close();

                        // 返回下载成功标识
                        return "success";
                    }
                });
    }

    public String uploadCommonFile(File file, String fileType) throws Exception {
        Set<MetaData> metaDataSet = new HashSet<>();
        StorePath storePath = fastFileStorageClient.uploadFile(new FileInputStream(file),
                file.length(), fileType, metaDataSet);
        return storePath.getPath();
    }

}
